package com.iocup.keybastion.core.impl;


import com.github.sd4324530.jtuple.Tuple2;
import com.iocup.keybastion.authentication.TokenService;
import com.iocup.keybastion.authentication.authenticator.AbstractAuthenticator;
import com.iocup.keybastion.authentication.authenticator.Authenticator;
import com.iocup.keybastion.authentication.authenticator.LoginCredentials;
import com.iocup.keybastion.common.Cookie;
import com.iocup.keybastion.configuration.TokenExtractProperties;
import com.iocup.keybastion.configuration.TokenProperties;
import com.iocup.keybastion.context.SecurityContextHolder;
import com.iocup.keybastion.context.WebContextHolder;
import com.iocup.keybastion.core.LoginLogic;
import com.iocup.keybastion.authentication.AccessToken;
import com.iocup.keybastion.core.profile.UserProfile;
import com.iocup.keybastion.core.session.Session;
import com.iocup.keybastion.utils.NameUtils;
import com.iocup.keybastion.exception.AuthenticationException;
import com.iocup.keybastion.exception.CredentialsEmptyException;
import com.iocup.keybastion.exception.CredentialsErrorException;
import com.pine.sunflower.core.validate.Validator;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

/**
 * @author xyjxust
 * @create 2022/3/4 14:03
 **/
public class DefaultLoginLogic implements LoginLogic {

    private Map<String, AbstractAuthenticator> authenticatorMap = new HashMap<>();

    private TokenProperties tokenProperties = new TokenProperties();

    private TokenExtractProperties tokenExtractProperties = new TokenExtractProperties();

    private TokenService tokenService;

    public void setTokenProperties(TokenProperties tokenProperties) {
        Validator.build().validate(tokenProperties == null, "tokenProperties不能为空")
                .throwIfFail();

        this.tokenProperties = tokenProperties;
    }

    public void setTokenExtractProperties(TokenExtractProperties tokenExtractProperties) {
        Validator.build().validate(StringUtils.isEmpty(tokenExtractProperties.getHeaderName()), "headerName不能为空").throwIfFail();
        this.tokenExtractProperties = tokenExtractProperties;
    }

    public void setTokenService(TokenService tokenService) {
        Validator.build().validate(tokenService == null, "tokenService不能为空")
                .throwIfFail();
        this.tokenService = tokenService;
    }

    public DefaultLoginLogic(Collection<AbstractAuthenticator> authenticators) {
        if (authenticators == null || authenticators.isEmpty()) {
            throw new IllegalArgumentException("authenticators不能为空");
        }
        authenticators.forEach(a -> authenticatorMap.put(a.getType(), a));
    }


    @Override
    public Object login(String authType) {
        WebContextHolder webContextHolder = SecurityContextHolder.getContext().getWebContextHolder();

        if (webContextHolder == null) {
            throw new SecurityException("未查找到请求上下文");
        }
        if (StringUtils.isBlank(authType)) {
            authType = NameUtils.getAuthName(webContextHolder);
        }
        Authenticator authenticator = this.authenticatorMap.get(authType);
        if (authenticator == null) {
            throw new AuthenticationException("未找到认证类型");
        }
        //开始获取凭证信息
        Optional<LoginCredentials> credentialsOptional = authenticator.getCredentials(webContextHolder);
        //凭证信息不存在就报错
        if (!credentialsOptional.isPresent()) {
            throw new CredentialsEmptyException("登录凭证为空");
        }
        LoginCredentials credentials = credentialsOptional.get();
        //通过凭证信息获取该凭证的用户信息
        Optional<Tuple2<UserProfile, Session>> optionalObjects = authenticator.authenticate(credentials, webContextHolder);
        if (!optionalObjects.isPresent()) {
            throw new CredentialsErrorException("登录凭证错误");
        }
        sessionStorageToken(optionalObjects.get().second.getToken());
        return optionalObjects.get().second;


    }

    @Override
    public boolean isLogin() {
        //如果山下文中就已经登录那么就表示登录成功
        if (SecurityContextHolder.getContext().isAuth()) {
            return true;
        }
        //请求中获取token
        String token = getToken();
        //如果token都是空的那肯定是没登录
        if (StringUtils.isBlank(token)) {
            return false;
        }
        //如果token存在时且能在会话中有，那么此时就是登录的
        Session session = tokenService.getByToken(token);
        if (session != null) {
            //把数据设置到上下文中
            SecurityContextHolder.getContext().setToken(session.getToken());
            SecurityContextHolder.getContext().setUserProfile(session.getUserProfile());
            return true;
        }
        //否在就没有登录
        return false;
    }

    @Override
    public String getToken() {
        Optional<String> token = querySessionStorageToken();
        if (token.isPresent()) {
            return token.get();
        }
        return null;
    }

    @Override
    public Session saveUserProfile(UserProfile userProfile) {
        Session session = this.tokenService.createAndSave(userProfile);
        if (session == null) {
            throw new AuthenticationException("创建会话失败");
        }
        sessionStorageToken(session.getToken());
        SecurityContextHolder.getContext().setToken(session.getToken());
        SecurityContextHolder.getContext().setUserProfile(session.getUserProfile());
        return session;
    }


    private void sessionStorageToken(AccessToken accessToken) {
        WebContextHolder webContextHolder = SecurityContextHolder.getContext().getWebContextHolder();
        webContextHolder.setRequestAttribute(NameUtils.buildStorageKey(webContextHolder), accessToken.getAccessToken());
        if (this.tokenProperties.getToken2Cookie().isEnabled()) {
            Cookie cookie = new Cookie(this.tokenExtractProperties.getCookieName(), accessToken.getAccessToken());
            cookie.setDomain(this.tokenProperties.getToken2Cookie().getDomain());
            cookie.setPath(this.tokenProperties.getToken2Cookie().getPath());
            cookie.setMaxAge(accessToken.getExpiresIn());
            cookie.setSecure(this.tokenProperties.getToken2Cookie().getSecure());
            cookie.setHttpOnly(this.tokenProperties.getToken2Cookie().getHttpOnly());
            cookie.setSameSitePolicy(this.tokenProperties.getToken2Cookie().getSameSite());
            webContextHolder.addResponseCookie(cookie);
        }
        webContextHolder.setResponseHeader(this.tokenExtractProperties.getHeaderName(),accessToken.getAccessToken());
    }

    private Optional<String> querySessionStorageToken() {
        WebContextHolder webContextHolder = SecurityContextHolder.getContext().getWebContextHolder();
        if (webContextHolder == null) {
            throw new SecurityException("未查找到请求上下文");
        }
        if (SecurityContextHolder.getContext().isAuth()) {
            return Optional.of(SecurityContextHolder.getContext().getToken().getAccessToken());
        }
        Optional<String> optionalS = webContextHolder.getRequestHeader(this.tokenExtractProperties.getHeaderName());
        if (!optionalS.isPresent()) {
            optionalS = webContextHolder.getRequestHeader(this.tokenExtractProperties.getHeaderName().toLowerCase(Locale.ROOT));
        }
        if (optionalS.isPresent()) {
            String headerWithoutPrefix = optionalS.get();
            if (StringUtils.isNotEmpty(this.tokenExtractProperties.getPrefixHeader())) {
                headerWithoutPrefix = headerWithoutPrefix.substring(this.tokenExtractProperties.getPrefixHeader().length());
            }
            if (this.tokenExtractProperties.isHeaderTrimValue()) {
                headerWithoutPrefix = headerWithoutPrefix.trim();
            }
            return Optional.ofNullable(headerWithoutPrefix);
        }
        optionalS = webContextHolder.getRequestAttribute(NameUtils.buildStorageKey(webContextHolder));
        if (!optionalS.isPresent() && !this.tokenExtractProperties.isCloseParameter()) {
            optionalS = webContextHolder.getRequestParameter(this.tokenExtractProperties.getParameterName());
        }
        if (!optionalS.isPresent() && this.tokenProperties.getToken2Cookie().isEnabled()) {
            optionalS = webContextHolder.getCookieValue(this.tokenExtractProperties.getCookieName());
        }
        return optionalS;
    }
}
